/*
 * JPEGImagesToVideo.java
 * 
 * From http://java.sun.com/javase/technologies/desktop/media/jmf/2.1.1/solutions/JpegImagesToMovie.java  1.3 01/03/13
 * 
 * Copyright (c) 1999-2001 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Sun grants you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software; and ii) Licensee does not utilize the software in a manner
 * which is disparaging to Sun.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY
 * OF SUCH DAMAGES.
 * 
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear facility.
 * Licensee represents and warrants that it will not use or redistribute the
 * Software for such purposes.
 */
package com.eteks.sweethome3d.swing;

import java.io.File;
import java.io.IOException;
import java.io.InterruptedIOException;

import javax.media.ConfigureCompleteEvent;
import javax.media.Controller;
import javax.media.ControllerEvent;
import javax.media.ControllerListener;
import javax.media.DataSink;
import javax.media.EndOfMediaEvent;
import javax.media.Format;
import javax.media.Manager;
import javax.media.MediaLocator;
import javax.media.NoDataSinkException;
import javax.media.NoProcessorException;
import javax.media.PrefetchCompleteEvent;
import javax.media.Processor;
import javax.media.RealizeCompleteEvent;
import javax.media.ResourceUnavailableEvent;
import javax.media.control.TrackControl;
import javax.media.datasink.DataSinkErrorEvent;
import javax.media.datasink.DataSinkEvent;
import javax.media.datasink.DataSinkListener;
import javax.media.datasink.EndOfStreamEvent;
import javax.media.protocol.ContentDescriptor;
import javax.media.protocol.DataSource;
import javax.media.protocol.FileTypeDescriptor;

/**
 * This program takes a list of images and convert them into a
 * QuickTime movie.
 */
public class JPEGImagesToVideo
{
	private Object waitSync;
	private boolean stateTransitionOK;
	private Object waitFileSync;
	private boolean fileDone;
	private String fileError;
	
	/**
	 * Creates a video file at the given size and frame rate. 
	 */
	public void createVideoFile(int width, int height, int frameRate, DataSource dataSource, File file)
			throws IOException
	{
		this.waitSync = new Object();
		this.stateTransitionOK = true;
		this.waitFileSync = new Object();
		this.fileDone = false;
		this.fileError = null;
		
		ControllerListener controllerListener = new ControllerListener()
		{
			public void controllerUpdate(ControllerEvent ev)
			{
				if (ev instanceof ConfigureCompleteEvent || ev instanceof RealizeCompleteEvent
						|| ev instanceof PrefetchCompleteEvent)
				{
					synchronized (waitSync)
					{
						stateTransitionOK = true;
						waitSync.notifyAll();
					}
				}
				else if (ev instanceof ResourceUnavailableEvent)
				{
					synchronized (waitSync)
					{
						stateTransitionOK = false;
						waitSync.notifyAll();
					}
				}
				else if (ev instanceof EndOfMediaEvent)
				{
					ev.getSourceController().stop();
					ev.getSourceController().close();
				}
			}
		};
		DataSinkListener dataSinkListener = new DataSinkListener()
		{
			public void dataSinkUpdate(DataSinkEvent ev)
			{
				if (ev instanceof EndOfStreamEvent)
				{
					synchronized (waitFileSync)
					{
						fileDone = true;
						waitFileSync.notifyAll();
					}
				}
				else if (ev instanceof DataSinkErrorEvent)
				{
					synchronized (waitFileSync)
					{
						fileDone = true;
						fileError = "Data sink error";
						waitFileSync.notifyAll();
					}
				}
			}
		};
		Processor processor = null;
		DataSink dataSink = null;
		try
		{
			processor = Manager.createProcessor(dataSource);
			processor.addControllerListener(controllerListener);
			processor.configure();
			// Put the Processor into configured state so we can set
			// some processing options on the processor.
			if (!waitForState(processor, Processor.Configured))
			{
				throw new IOException("Failed to configure the processor.");
			}
			
			// Set the output content descriptor to QuickTime.
			processor.setContentDescriptor(new ContentDescriptor(FileTypeDescriptor.QUICKTIME));
			
			// Query for the processor for supported formats.
			// Then set it on the processor.
			TrackControl trackControls[] = processor.getTrackControls();
			Format format[] = trackControls[0].getSupportedFormats();
			if (format == null || format.length <= 0)
			{
				throw new IOException("The mux does not support the input format: " + trackControls[0].getFormat());
			}
			
			trackControls[0].setFormat(format[0]);
			
			// We are done with programming the processor. Let's just realize it.
			processor.realize();
			if (!waitForState(processor, Controller.Realized))
			{
				throw new IOException("Failed to realize the processor.");
			}
			
			// Now, we'll need to create a DataSink.
			// Caution: do not use file.toURI().toURL() with JMF
			dataSink = Manager.createDataSink(processor.getDataOutput(), new MediaLocator(file.toURL()));
			dataSink.open();
			dataSink.addDataSinkListener(dataSinkListener);
			this.fileDone = false;
			
			// Start the actual transcoding
			processor.start();
			dataSink.start();
			
			// Wait for EndOfStream event.
			synchronized (this.waitFileSync)
			{
				while (!this.fileDone)
				{
					this.waitFileSync.wait();
				}
			}
			
			if (this.fileError != null)
			{
				throw new IOException(this.fileError);
			}
		}
		catch (NoProcessorException ex)
		{
			IOException ex2 = new IOException(ex.getMessage());
			ex2.initCause(ex);
			throw ex2;
		}
		catch (NoDataSinkException ex)
		{
			IOException ex2 = new IOException("Failed to create a DataSink for the given output MediaLocator");
			ex2.initCause(ex);
			throw ex2;
		}
		catch (InterruptedException ex)
		{
			if (dataSink != null)
			{
				dataSink.stop();
			}
			throw new InterruptedIOException("Video creation interrupted");
		}
		finally
		{
			if (dataSink != null)
			{
				dataSink.close();
				dataSink.removeDataSinkListener(dataSinkListener);
			}
			if (processor != null)
			{
				processor.close();
				processor.removeControllerListener(controllerListener);
			}
		}
	}
	
	/**
	 * Blocks until the processor has transitioned to the given state. Return false
	 * if the transition failed.
	 */
	private boolean waitForState(Processor p, int state) throws InterruptedException
	{
		synchronized (waitSync)
		{
			while (p.getState() < state && stateTransitionOK)
			{
				waitSync.wait();
			}
		}
		return stateTransitionOK;
	}
}
